package com.gitee.wsl.compose.ext.flow

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlin.coroutines.CoroutineContext
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext
import kotlin.coroutines.EmptyCoroutineContext


@Composable
fun <T> StateFlow<T>.collectAsStateValue(
    context: CoroutineContext = Dispatchers.Default,
): T = collectAsState(context).value

@Composable
fun <T : R, R> Flow<T>.collectAsStateValue(
    initial: R,
    context: CoroutineContext = Dispatchers.Default,
): R = collectAsState(initial, context).value



@Composable
fun <T> rememberFlow(
    flow: Flow<T>,
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    minActiveState: Lifecycle.State = Lifecycle.State.STARTED
): Flow<T> {
    return remember(key1 = flow, key2 = lifecycleOwner) {
        flow.flowWithLifecycle(lifecycleOwner.lifecycle, minActiveState)
    }
}

@Composable
fun <T> Flow<T>.collectAsStateWithLifecycle(
    initialValue: T,
    lifecycle: Lifecycle = androidx.compose.ui.platform.LocalLifecycleOwner.current.lifecycle,
    minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
    context: CoroutineContext = EmptyCoroutineContext
): State<T> = flowWithLifecycle(lifecycle,minActiveState).collectAsState(
    initialValue,
    context
)

@Composable
fun <T : R, R> Flow<T>.collectAsStateLifecycleAware(
    initial: R,
    context: CoroutineContext = EmptyCoroutineContext
): State<R> {
    val lifecycleAwareFlow = rememberFlow(flow = this)
    return lifecycleAwareFlow.collectAsState(initial = initial, context = context)
}

@Suppress("StateFlowValueCalledInComposition")
@Composable
fun <T> StateFlow<T>.collectAsStateLifecycleAware(
    context: CoroutineContext = EmptyCoroutineContext
): State<T> = collectAsStateLifecycleAware(initial = value, context = context)

// Based on: https://stackoverflow.com/a/70185157/4385799 and https://medium.com/androiddevelopers/a-safer-way-to-collect-flows-from-android-uis-23080b1f8bda
@Composable
inline fun <reified T> Flow<T>.collectWithLifecycle(
    key: Any = Unit,
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
    noinline action: suspend (T) -> Unit,
) {
    val lifecycleAwareFlow = remember(this, lifecycleOwner) {
        flowWithLifecycle(
            lifecycle = lifecycleOwner.lifecycle,
            minActiveState = minActiveState,
        )
    }

    LaunchedEffect(key) {
        lifecycleAwareFlow.collect { action(it) }
    }
}

inline fun <T, R> StateFlow<T>.mapState(
    scope: CoroutineScope,
    crossinline mapper: (T) -> R
): StateFlow<R> = map { mapper(it) }
    .distinctUntilChanged()
    .stateIn(
        scope = scope,
        initialValue = mapper(value),
        started = SharingStarted.WhileSubscribed(2000),
    )

fun <T> Flow<T>.createMutableStateFlow(
    initialValue: T,
    updater: (T) -> Unit,
    scope: CoroutineScope,
): MutableStateFlow<T> {
    val downStream = MutableStateFlow(initialValue)
    onEach { newFromUpStream ->
        downStream.update { newFromUpStream }
    }.launchIn(scope)
    downStream.onEach {
        updater(it)
    }.launchIn(scope)
    return downStream
}


fun <T> StateFlow<T>.createMutableStateFlow(
    updater: suspend (T) -> Unit,
    scope: CoroutineScope,
): MutableStateFlow<T> {
    val downStream = MutableStateFlow(value)
    onEach { newFromUpStream ->
        downStream.update { newFromUpStream }
    }.launchIn(scope)
    downStream.onEach {
        updater(it)
    }.launchIn(scope)
    return downStream
}

/**
 * Each value of [this] [Flow] will trigger [applyDiff] to the result [SnapshotStateList]
 *
 * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [SnapshotStateList]
 * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
 * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
 * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
 */
/*
@Suppress("NOTHING_TO_INLINE")
inline fun <reified T> Flow<List<T>>.asMutableComposeListState(
    scope: CoroutineScope,
    useContextOnChange: CoroutineContext? = Dispatchers.Main,
    noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull,
): SnapshotStateList<T> {
    val state = mutableStateListOf<T>()
    val changeBlock: suspend (List<T>) -> Unit = useContextOnChange ?.let {
        {
            withContext(useContextOnChange) {
                state.applyDiff(it)
            }
        }
    } ?: {
        state.applyDiff(it)
    }
    subscribeSafelyWithoutExceptions(scope, onException, changeBlock)
    return state
}*/


/**
 * @author Vivien Mahe
 * @since 26/08/2023
 */

@Composable
inline fun <T> SharedFlow<T>.subscribe(crossinline action: (T) -> Unit) {
    val lifecycleOwner = LocalLifecycleOwner.current

    LaunchedEffect(this) {
        lifecycleOwner.repeatOnLifecycle(state = Lifecycle.State.STARTED) {
            this@subscribe.collect { action(it) }
        }
    }
}
